package party.loveit.download.core;


import android.content.Context;
import android.os.Process;
import android.text.TextUtils;
import android.util.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.UUID;

import static party.loveit.download.core.DLCons.Base.DEFAULT_TIMEOUT;
import static party.loveit.download.core.DLCons.Base.LENGTH_PER_THREAD;
import static party.loveit.download.core.DLCons.Base.MAX_REDIRECTS;
import static party.loveit.download.core.DLCons.Code.HTTP_MOVED_PERM;
import static party.loveit.download.core.DLCons.Code.HTTP_MOVED_TEMP;
import static party.loveit.download.core.DLCons.Code.HTTP_NOT_MODIFIED;
import static party.loveit.download.core.DLCons.Code.HTTP_OK;
import static party.loveit.download.core.DLCons.Code.HTTP_PARTIAL;
import static party.loveit.download.core.DLCons.Code.HTTP_SEE_OTHER;
import static party.loveit.download.core.DLCons.Code.HTTP_TEMP_REDIRECT;
import static party.loveit.download.core.DLError.ERROR_CREATE_FILE;
import static party.loveit.download.core.DLError.ERROR_INVALID_URL;
import static party.loveit.download.core.DLError.ERROR_OPEN_CONNECT;
import static party.loveit.download.core.DLError.ERROR_UNHANDLED_REDIRECT;


class DLTask implements Runnable, IDLThreadListener {
    private static final String TAG = DLManager.TAG;

    private DLInfo info;
    private Context context;

    private long totalProgress;
    private int count;
    private long lastTime = System.currentTimeMillis();
    private long lasttotalProgress;

    DLTask(Context context, DLInfo info) {
        this.info = info;
        this.context = context;
        this.totalProgress = info.currentBytes;
        lasttotalProgress = info.currentBytes;
        //if (!info.isResume) DLDBManager.getInstance(context).insertTaskInfo(info);
    }

    @Override
    public synchronized void onProgress(long progress) {
        info.dlState = DLStates.DL_DLING;
        totalProgress += progress;
        long currentTime = System.currentTimeMillis();
        long useTime = currentTime - lastTime;
        if (useTime > 1000) {
            Log.d(TAG, totalProgress + "");
            String speed;
            int currentSpeed = (int) ((totalProgress - lasttotalProgress) / 1024 / (useTime / 1000));
            speed = currentSpeed + "kb/s";
            if (currentSpeed >= 1024) {
                float fSpeed = DLUtil.formatFloat((float) currentSpeed / (float) 1024.00);
                speed = fSpeed + "m/s";
            }
            info.speed = speed;
            if (info.hasListener) info.listener.onProgress(info.baseUrl, speed, totalProgress);
            lastTime = currentTime;
            lasttotalProgress = totalProgress;
            info.currentBytes = totalProgress;
            DLDBManager.getInstance(context).updateTaskInfo(info);
        }
    }

    @Override
    public synchronized void onStop(DLThreadInfo threadInfo) {
        if (null == threadInfo) {
            DLManager.getInstance().removeDLTask(info.baseUrl);
            DLDBManager.getInstance(context).deleteTaskInfo(info.baseUrl);
            if (info.hasListener) {
                info.listener.onProgress(info.baseUrl, "0kb/s", info.totalBytes);
                info.listener.onStop(info.baseUrl, info.totalBytes);
            }
            return;
        }
        DLDBManager.getInstance(context).updateThreadInfo(threadInfo);
        count++;
        if (count >= info.threads.size()) {
            Log.d(TAG, "All the threads was stopped.");
            info.currentBytes = totalProgress;
            DLManager.getInstance().addStopTask(info).removeDLTask(info.baseUrl);
            DLDBManager.getInstance(context).updateTaskInfo(info);
            DLManager.getInstance().addDLTask();
            count = 0;
            info.dlState = DLStates.DL_STOP;
            if (info.hasListener) info.listener.onStop(info.baseUrl, totalProgress);
        }
    }

    @Override
    public synchronized void onFinish(DLThreadInfo threadInfo) {
        if (null == threadInfo) {
            DLManager.getInstance().removeDLTask(info.baseUrl);
            DLDBManager.getInstance(context).deleteTaskInfo(info.baseUrl);
            if (info.hasListener) {
                info.listener.onProgress(info.baseUrl, "0kb/s", info.totalBytes);
                info.listener.onFinish(info.baseUrl, info.file);
            }
            return;
        }
        info.removeDLThread(threadInfo);
        DLDBManager.getInstance(context).deleteThreadInfo(threadInfo.id);
        Log.d(TAG, "Thread size " + info.threads.size());
        if (info.threads.isEmpty()) {
            Log.d(TAG, "Task was finished." + info.totalBytes);
            DLManager.getInstance().removeDLTask(info.baseUrl);
            info.dlState = DLStates.DL_COMPLETE;
            DLDBManager.getInstance(context).updateTaskInfo(info);
            //DLDBManager.getInstance(context).deleteTaskInfo(info.baseUrl);
            if (info.hasListener) {
                info.listener.onProgress(info.baseUrl, "0kb/s", info.totalBytes);
                info.listener.onFinish(info.baseUrl, info.file);
                //DLManager.getInstance().aa();
            }
            DLManager.getInstance().addDLTask();
        }
    }

    @Override
    public void onError(String error) {
        info.dlState = DLStates.DL_ERROR;
        if (info.hasListener) {
            info.listener.onError(info.baseUrl, ERROR_OPEN_CONNECT, error);
        }
    }

    @Override
    public void run() {
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        while (info.redirect < MAX_REDIRECTS) {
            HttpURLConnection conn = null;
            try {
                conn = (HttpURLConnection) new URL(info.realUrl).openConnection();
                conn.setInstanceFollowRedirects(false);
                conn.setConnectTimeout(DEFAULT_TIMEOUT);
                conn.setReadTimeout(DEFAULT_TIMEOUT);

                addRequestHeaders(conn);

                final int code = conn.getResponseCode();
                Log.d("AigeStudio", code + "");
                switch (code) {
                    case HTTP_OK:
                    case HTTP_PARTIAL:
                        dlInit(conn, code);
                        return;
                    case HTTP_MOVED_PERM:
                    case HTTP_MOVED_TEMP:
                    case HTTP_SEE_OTHER:
                    case HTTP_NOT_MODIFIED:
                    case HTTP_TEMP_REDIRECT:
                        final String location = conn.getHeaderField("location");
                        if (TextUtils.isEmpty(location)) {
                            info.dlState = DLStates.DL_ERROR;
                            if (info.hasListener)
                                info.listener.onError(info.baseUrl, ERROR_UNHANDLED_REDIRECT, "Can not obtain real url from location in header");
                            DLManager.getInstance().removeDLTask(info.baseUrl);
                            DLManager.getInstance().addDLTask();

                            return;
                        }
                        info.realUrl = location;
                        info.redirect++;
                        continue;
                    default:
                        info.dlState = DLStates.DL_ERROR;
                        if (info.hasListener)
                            info.listener.onError(info.baseUrl, code, conn.getResponseMessage());
                        DLManager.getInstance().removeDLTask(info.baseUrl);
                        DLManager.getInstance().addDLTask();

                        return;
                }
            } catch (Exception e) {
                info.dlState = DLStates.DL_ERROR;
                if (info.hasListener)
                    info.listener.onError(info.baseUrl, ERROR_OPEN_CONNECT, e.toString());
                DLManager.getInstance().removeDLTask(info.baseUrl);
                DLManager.getInstance().addDLTask();
                return;
            } finally {
                if (null != conn) conn.disconnect();
            }
        }
        info.dlState = DLStates.DL_ERROR;
        if (info.hasListener)
            info.listener.onError(info.baseUrl, ERROR_UNHANDLED_REDIRECT, "too much redirect");
        DLManager.getInstance().removeDLTask(info.baseUrl);
        DLManager.getInstance().addDLTask();


    }

    private void dlInit(HttpURLConnection conn, int code) throws Exception {
        readResponseHeaders(conn);
        DLDBManager.getInstance(context).updateTaskInfo(info);
        if (!DLUtil.createFile(info.dirPath, info.fileName)) {
            info.dlState = DLStates.DL_ERROR;
            if (info.hasListener)
                info.listener.onError(info.baseUrl, ERROR_CREATE_FILE, "can not new file");
            DLManager.getInstance().removeDLTask(info.baseUrl);
            DLManager.getInstance().addDLTask();
            return;
        }
        info.file = new File(info.dirPath, info.fileName);
        if (info.file.exists() && info.file.length() == info.totalBytes) {
            if (info.hasListener) {
                info.listener.onStart(info.fileName, info.baseUrl, info.totalBytes);
                info.listener.onProgress(info.baseUrl, "0kb/s", info.totalBytes);
                info.listener.onFinish(info.baseUrl, info.file);
            }
            info.dlState = DLStates.DL_COMPLETE;
            DLDBManager.getInstance(context).updateTaskInfo(info);

            DLManager.getInstance().removeDLTask(info.baseUrl);
            DLManager.getInstance().addDLTask();

            return;
        }
        if (info.hasListener) info.listener.onStart(info.fileName, info.baseUrl, info.totalBytes);
        switch (code) {
            case HTTP_OK:
                dlData(conn);
                break;
            case HTTP_PARTIAL:
                if (info.totalBytes <= 0) {
                    dlData(conn);
                    break;
                }
                if (info.isResume) {
                    for (DLThreadInfo threadInfo : info.threads) {
                        Log.i(TAG, "DLThreadInfo threadInfo : info.threads");
                        DLManager.getInstance()
                                .addDLThread(new DLThread(context, threadInfo, info, this));
                    }
                    break;
                }
                dlDispatch();
                break;
        }
    }

    private void dlDispatch() {
        long threadSize;
        long threadLength = LENGTH_PER_THREAD;
        if (info.totalBytes <= LENGTH_PER_THREAD) {
            threadSize = 2;
        } else {
            threadSize = info.totalBytes / LENGTH_PER_THREAD;
            if (threadSize > DLManager.getInstance().getMaxThread())
                threadSize = DLManager.getInstance().getMaxThread();
        }
        threadLength = info.totalBytes / threadSize;
        for (int i = 0; i < threadSize; i++) {
            long start = i * threadLength;
            long end = start + threadLength - 1;
            if (i == threadSize - 1) {
                end = info.totalBytes;
            }
            DLThreadInfo threadInfo =
                    new DLThreadInfo(UUID.randomUUID().toString(), info.baseUrl, start, end);
            Log.i(TAG, "start-->" + start + "  end-->" + end);
            info.addDLThread(threadInfo);
            DLDBManager.getInstance(context).insertThreadInfo(threadInfo);
            DLManager.getInstance().addDLThread(new DLThread(context, threadInfo, info, this));
        }
    }

    private void dlData(HttpURLConnection conn) throws IOException {
        InputStream is = conn.getInputStream();
        FileOutputStream fos = new FileOutputStream(info.file);
        byte[] b = new byte[4096];
        int len;
        while (!info.isStop && (len = is.read(b)) != -1) {
            fos.write(b, 0, len);
            onProgress(len);
        }
        if (!info.isStop) {
            onFinish(null);
        } else {
            onStop(null);
        }
        fos.close();
        is.close();
    }

    private void addRequestHeaders(HttpURLConnection conn) {
        for (DLHeader header : info.requestHeaders) {
            conn.addRequestProperty(header.key, header.value);
        }
    }

    private void readResponseHeaders(HttpURLConnection conn) {
        info.disposition = conn.getHeaderField("Content-Disposition");
        info.location = conn.getHeaderField("Content-Location");
        info.mimeType = DLUtil.normalizeMimeType(conn.getContentType());
        final String transferEncoding = conn.getHeaderField("Transfer-Encoding");
        if (TextUtils.isEmpty(transferEncoding)) {
            try {
                info.totalBytes = Long.parseLong(conn.getHeaderField("Content-Length"));
            } catch (NumberFormatException e) {
                info.totalBytes = -1;
            }
        } else {
            info.totalBytes = -1;
        }
        if (info.totalBytes == -1 && (TextUtils.isEmpty(transferEncoding) ||
                !transferEncoding.equalsIgnoreCase("chunked"))) {
           // throw new RuntimeException("Can not obtain size of download file.");
            info.dlState = DLStates.DL_ERROR;
            if (info.hasListener)
                info.listener.onError(info.baseUrl, ERROR_INVALID_URL, "Can not obtain size of download file.");
            DLManager.getInstance().removeDLTask(info.baseUrl);
            DLManager.getInstance().addDLTask();
            return;
        }
        if (TextUtils.isEmpty(info.fileName))
            info.fileName = DLUtil.obtainFileName(info.realUrl, info.disposition, info.location);
    }
}